// Simple unit test framework.

#include "unitTest.h"
#include "timing.h"

#include <math.h>
#include <stdio.h>

namespace simple_test{

    //
    // functions of UnitTestFunction
    //
    UnitTestFunction::UnitTestFunction (const std::string& name)
    : _result (eNotRun), _name (name), _detail (false), _elapsed (0) {}

    // 根据detail参数，会有两种输出格式。
    // detail == false，则只在出现错误的时候输出“Exception 'XXXX' caught”
    //   或者"Unknown exception caught"；
    // detail == true ，则无论是否成功都输入详细的调试信息。
    void UnitTestFunction::run (bool detail)
    {
        std::string error;
        _detail = detail;

        // Run the test
        if (detail) 
            std::cout << "Running " << _name.c_str() << std::endl;
        _result = eRunning;

        ElapsedTime time;
        try {
            test ();
        }
        catch (const std::exception& ex) {
            // We caught an STL exception
            error = std::string("Exception '") + ex.what() + "' caught";
            if (detail)
                std::cout << error << " in " << _name.c_str() << std::endl;
            
            addMessage (error);
            _result = eFailure;
        }
        catch (...) {
            // We caught an unknown exception
            error = "Unknown exception caught";
            if (detail)
            std::cout << error << " in " << _name.c_str() << std::endl;

            addMessage (error);
            _result = eFailure;
        }
        _elapsed = time.sec ();

        // Make sure the test() function set a result or set eUnknown
        if (_result != eSuccess && _result != eFailure)
            _result = eUnknown;

        if (_detail) {
            std::cout << "  Result of " << _name.c_str() << " is " 
            << resultString().c_str() << " (" << _elapsed 
            << " sec)" << std::endl;

            if (_message.size() > 0)
                std::cout << "  Messages: " << _message.c_str() 
                << std::endl;
        }
    }


    bool UnitTestFunction::verify (bool state, const std::string& message)
    {
        if (!state) {
            _result = eFailure;
            addMessage (message);
            if (_detail) 
                std::cout << " FAILURE " << _name.c_str() << " : " 
                << message.c_str() << std::endl;
        }
        else if (_result != eFailure)
            // Make sure we mark the unit test success, if possible.
            // 因为初始的时候，_result是eRunning或eNotRun
            _result = eSuccess;
        return state;
    }

    bool UnitTestFunction::verifyFloat (double d1, double d2)
    {
        double delta = fabs (d1-d2);
        if (delta > 1.e-5) {
            char buffer[128];
            sprintf (buffer, "%.11f != %.11f", d1, d2);
            return verify (false, buffer);
        }

        return verify (true);
    }


    void UnitTestFunction::addMessage (const std::string& message)
    {
        if (_message.size() > 0) _message += "\n";
            _message += message;
    }


    
    // Not a const reference because it isn't safe
    std::string UnitTestFunction::resultString () const
    {
        switch (_result) {
            case eNotRun:
                return "Not Run";
            case eRunning:
                return "Running";
            case eUnknown:
                return "Unknown";
            case eSuccess:
                return "Success";
            case eFailure:
                return "***** Failure *****";
            default:
                break;
        }

        return "Unknown Result";
    }

    //
    // functions of UnitTest
    //
    UnitTest* UnitTest::_sOnly = 0;

    UnitTest& UnitTest::gOnly ()
    {
        if (!_sOnly)  _sOnly = new UnitTest ();
            return *_sOnly;
    }

    UnitTest::UnitTest () : _start (0), _stop (0)  {}

    void UnitTest::addTest (const std::string& /*name*/, 
    UnitTestFunction* test)
    {
        // Keep track of this unit test object to run
        _tests.push_back (test);
    }

    bool UnitTest::run (bool detail)
    {
        bool state = true;
        _start = time(0);

        for (unsigned int i=0; i<_tests.size(); i++) {
            // Fetch the next test to run and run it
            UnitTestFunction* test = _tests[i];
            test->run (detail);
            if (test->result() != UnitTestFunction::eSuccess) 
                state = false;
        }

        _stop = time(0);
        return state;
    }


    const UnitTestFunction* UnitTest::find (int index) const
    {
        if (index < 0 || index >= size()) return 0;
            return _tests[index];
    }


    void UnitTest::dumpResults (std::ostream& out)
    {
        int nSuccess = 0;
        int nFailed  = 0;
        int nOther   = 0;

        out << "Unit Test started at  " << ctime(&_start) << std::endl;
        out << "Unit Test finished at " << ctime(&_stop) << std::endl;

        for (unsigned int i=0; i<_tests.size(); i++) {
            UnitTestFunction* test = _tests[i];

            switch (test->result()) {
                case UnitTestFunction::eSuccess:
                    nSuccess++;
                    break;
                case UnitTestFunction::eFailure:
                    nFailed++;
                    break;
                default:
                    nOther++;
                    break;
            };

            out << "Test " << i+1 << ": " << test->resultString().c_str() 
                << " : " << test->name().c_str() << " : ";

            if (test->description().size() > 0)
                out << test->description().c_str();

            out << " : " << test->elapsed() << " sec" << std::endl;

            if (test->message().size() > 0)
                out << "  Messages: " << std::endl 
                    << test->message().c_str() << std::endl;
        }

        // 总数统计
        out << std::endl << std::endl;
        out << "Passed: " << nSuccess << ", Failed: " << nFailed 
            << ", Other: " << nOther << std::endl;
    }
}